查看原文
其他

码图并茂红黑树

2018-03-30 又出bug了 看雪学院

转眼就快毕业了,既然准备找工作听说最好发点帖子,那么第一篇就拿数据结构开刀吧!
  

当初自己想写红黑树时候参考了网上的代码,最后发现还是<<算法导论>中的伪代码最好懂,所以代码完全按照<<算法导论>>伪代码来编写,方便对照<<算法导论>>来学习红黑树。


写红黑树的文章很多,这篇文章和其他写红黑树的区别:

1. 完全按照<<算法导论>>中伪代码逻辑编写,网上很多代码都经历过优化或者加工,对照<<算法导论>>的伪代码看着很难受。

2. 后面提供插入和删除调整树的图,对着代码单步调试更好理解。

    

直接上代码

    

RBTree.h


#pragma once
//红黑树
class RBTree
{

private:
   typedef enum
   {
       E_TREE_BLACK,
       E_TREE_RED,
       E_TREE_COLOR_MAX
   }ETreeColor;
   const static char *s_pszColor[E_TREE_COLOR_MAX];
   typedef struct  __TreeNode
   {

       __TreeNode* pParent;
       __TreeNode* pLeft;
       __TreeNode* pRight;
       ETreeColor eColor;
       int nValue;
   }TreeNode, *PTreeNode;

public:
   RBTree();
   ~RBTree();
   //插入数据
   void InsertData(int nValue);  //插入数据
   bool Empty();  //判空
   bool GetMax(PTreeNode pNode, int &nMax); // 获取最大值
   bool GetMin(PTreeNode pNode, int &nMin); //获取最小值
   void DeleteElement(int nDelete);  //删除指定的元素
   bool FindElement(int nFindValue); //查找数据,如果查找到返回true,否则返回false
   void BreadthEnum();  //广度遍历
private:
   void InsertFixUp(PTreeNode pInsertNode); //插入pInsertNode点后调整红黑树
   void DeleteFixUp(PTreeNode pFixNode); //删除后重新调整
   void SingleL(PTreeNode &pNode, PTreeNode &newTop); //左旋转,并且返回新的顶点
   void SingleR(PTreeNode &pNode, PTreeNode &newTop); //右旋转
   void ReplaceParent(PTreeNode pBeReplacedNode, PTreeNode pReplaceNode); //把pReplaceNode的父节点修改为pBeReplacedNode的
   bool GetMinNode(PTreeNode pNode, PTreeNode &pMinNode);//获取最小的节点
private:
   PTreeNode m_pRoot;  //根节点指针
   PTreeNode m_pNil;  //空节点
};


RBTree.cpp   

#include "RBTree.h"
#include <queue>
#include <assert.h>
#include <iostream>

const char *  RBTree::s_pszColor[E_TREE_COLOR_MAX] = {"黑", "红"};


RBTree::RBTree()
{
   m_pRoot = nullptr;
   //
   m_pNil = new TreeNode();
   m_pNil->pLeft = nullptr;
   m_pNil->pRight = nullptr;
   m_pNil->pParent = nullptr;
   m_pNil->eColor = E_TREE_BLACK;
   m_pNil->nValue = 0xFFFFFFFF;


}


RBTree::~RBTree()
{
   if (!Empty())
   {
       std::queue<PTreeNode> nodeQue;
       nodeQue.push(m_pRoot);
       //广度遍历
       while (!nodeQue.empty())
       {
           PTreeNode pNode = nodeQue.front();
           PTreeNode pLeft = pNode->pLeft;
           PTreeNode pRight = pNode->pRight;
           //出队列释放资源
           nodeQue.pop();
           delete pNode;
           if (pLeft != m_pNil) nodeQue.push(pLeft);
           if (pRight != m_pNil) nodeQue.push(pRight);
       }
   }
   if (m_pNil)
   {
       delete m_pNil;
       m_pNil = nullptr;
   }
}

void RBTree::InsertData(int nValue)
{
   //1.如果是第一个节点
   if (Empty())
   {
       m_pRoot = new TreeNode();
       m_pRoot->eColor = E_TREE_BLACK;
       m_pRoot->nValue = nValue;
       m_pRoot->pLeft = m_pNil;
       m_pRoot->pRight = m_pNil;
       m_pRoot->pParent = m_pNil;
       return;
   }
   //2. 找到需要插入的位置pPreNode
   PTreeNode pPreNode = m_pRoot->pParent;
   PTreeNode pCurrent = m_pRoot;
   while (m_pNil != pCurrent)
   {
       pPreNode = pCurrent;
       if (nValue <= pCurrent->nValue)
       {
           pCurrent = pCurrent->pLeft;
       }
       else
       {
           pCurrent = pCurrent->pRight;
       }
   }
   //3. 把数据插入到正确的位置
   PTreeNode pInsertNode = new TreeNode();
   pInsertNode->eColor = E_TREE_RED;
   pInsertNode->nValue = nValue;
   pInsertNode->pLeft = m_pNil;
   pInsertNode->pRight = m_pNil;
   pInsertNode->pParent = pPreNode;
   //
  38 39255 38 14940 0 0 1355 0 0:00:28 0:00:11 0:00:17 2988  if (nValue <= pPreNode->nValue)
   {
       pPreNode->pLeft = pInsertNode;
   }
   else
   {
       pPreNode->pRight = pInsertNode;
   }
   //4. 从插入点开始调整红黑树
   InsertFixUp(pInsertNode);
}

bool RBTree::Empty()
{
   return nullptr == m_pRoot;
}

bool RBTree::GetMax(PTreeNode pNode, int &nMax)
{
   if (nullptr == pNode)
   {
       return false;
   }
   while (pNode)
   {
       nMax = pNode->nValue;
       pNode = pNode->pRight;
   }
   return true;
}

bool RBTree::GetMin(PTreeNode pNode, int &nMin)
{
   if (nullptr == pNode)
   {
       return false;
   }
   while (pNode)
   {
       nMin = pNode->nValue;
       pNode = pNode->pLeft;
   }
   return true;
}

void RBTree::DeleteElement(int nDelete)
{
   if (Empty())
       return;
   //1.先找到位置
   PTreeNode pCurrent = m_pRoot;
   PTreeNode pNeedDelNode = nullptr;
   while (m_pNil != pCurrent)
   {
       if (nDelete < pCurrent->nValue)
       {
           pCurrent = pCurrent->pLeft;
       }
       else if (nDelete > pCurrent->nValue)
       {
           pCurrent = pCurrent->pRight;
       }
       else
       {
           pNeedDelNode = pCurrent;
           break;
       }
   }
   //2. 如果未找到直接退出
   if (nullptr == pNeedDelNode) return;

   //3.执行删除,计算出pNeedDeleteNode,pRealDeleteNode,pFixUpNode
   /*
       上面传入删除点记着:pNeedDeleteNode
       实际删除点记着:pRealDeleteNode
       调整点位pRealDeleteNode的后继记着:pFixUpNode
   */

   PTreeNode pRealDeleteNode = nullptr;
   PTreeNode pFixUpNode = nullptr;
   ETreeColor eRealDeleteColor = E_TREE_COLOR_MAX;
   //3.1 如果左子树为空
   if (m_pNil == pNeedDelNode->pLeft)
   {
       pRealDeleteNode = pNeedDelNode;
       eRealDeleteColor = pRealDeleteNode->eColor;
       pFixUpNode = pRealDeleteNode->pRight;
       //
       ReplaceParent(pRealDeleteNode, pRealDeleteNode->pRight);

   }
   //3.2 如果右子树为空
   else if (m_pNil == pNeedDelNode->pRight)
   {
       pRealDeleteNode = pNeedDelNode;
       eRealDeleteColor = pRealDeleteNode->eColor;
       pFixUpNode = pRealDeleteNode->pLeft;
       //
       ReplaceParent(pRealDeleteNode, pRealDeleteNode->pLeft);
   }
   //3.3 如果左右子树都不为空
   else
   {
       //获取准备删除点的右子树最小节点,pRealDeleteNode一定不是m_nil
       bool bGetMinRet = GetMinNode(pNeedDelNode->pRight, pRealDeleteNode);
       assert(bGetMinRet);
       assert(pRealDeleteNode != m_pNil);
       eRealDeleteColor = pRealDeleteNode->eColor;
       //最小点左子树一定为m_nil,所以pRight是它的后继
       pFixUpNode = pRealDeleteNode->pRight;
       //主要是用最小点(pRealDeleteNode)来替换需要删除点(pNeedDelNode)的位置,分两种情况
       if (pRealDeleteNode->pParent == pNeedDelNode)
       {
           //情况一:
           /*
             >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
               pNeedDelNode
                  /                  \
                /                     \
           nodex           pRealDeleteNode
                                        /     \
                                      /         \
                                   nil          这个节点一定是红色节点或者空节点(X)(根据红黑树性质5)
              >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
              如果是红色节点:在调整的时候直接变黑
              如果是空节点:X就是调整的开始点
              >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
              最关键的问题是:为什么使用X点作为调整点而不是直接使用pRealDeleteNode作为调整点?
              1. X点和pRealDeleteNode都可以作为调整点
              2. 这里选pRealDeleteNode可能更好理解,但是选择X作为调整点有一个好处和情况2统一的处理方式
           */

           //因为pFixUpNode可能为m_pNil,m_Nil公用的所以需要调整下
           pFixUpNode->pParent = pRealDeleteNode;

       }
       else
       {
           //情况二:
           /*
           >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
             pNeedDelNode
              /                \
            /                   \
       nodex              node1
                            /           \
                          /               \
       pRealDeleteNode          node2
                       /    \
                     /        \
               m_nil       这个节点一定是红色节点或者空节点(X)(根据红黑树性质5)
           >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
           */

           //让pRealDeleteNode父节点指向 pRealDeleteNode->pRight
           ReplaceParent(pRealDeleteNode, pRealDeleteNode->pRight);
           //让pRealDeleteNode的右节点接管原来pNeedDelNode的右节点
           pRealDeleteNode->pRight = pNeedDelNode->pRight;
           //让pRealDeleteNode的右节点的父节点指向pRealDeleteNode(右子树一定不为m_pNil)
           pRealDeleteNode->pRight->pParent = pRealDeleteNode;
       }
       //让pNeedDelNode父节点指向pRealDeleteNode
       ReplaceParent(pNeedDelNode, pRealDeleteNode);
       //让pRealDeleteNode的左节点接管原来pNeedDelNode的右节点
       pRealDeleteNode->pLeft = pNeedDelNode->pLeft;
       //让pRealDeleteNode的左节点的父节点指向pRealDeleteNode(左子树一定部位m_pNil)
       pRealDeleteNode->pLeft->pParent = pRealDeleteNode;
       // 使用pNeedDelNode的颜色
       pRealDeleteNode->eColor = pNeedDelNode->eColor;
   }
   //4. 在pFixUpNode点执行调整
   if (E_TREE_BLACK == eRealDeleteColor)
   {
       DeleteFixUp(pFixUpNode);
   }
   //5. 处理根节点问题
   if (m_pRoot == m_pNil) m_pRoot = nullptr;
   //6. 清理掉删除的节点
   delete pNeedDelNode;

}

bool RBTree::FindElement(int nFindValue)
{
   if (Empty())
       return false;
   PTreeNode pCurrent = m_pRoot;
   while (m_pNil != pCurrent)
   {
       if (nFindValue < pCurrent->nValue)
       {
           pCurrent = pCurrent->pLeft;
       }
       else if (nFindValue > pCurrent->nValue)
       {
           pCurrent = pCurrent->pRight;
       }
       else
       {
           return true;
       }
   }
   return false;
}


void RBTree::BreadthEnum()
{
   if (Empty()) return;
   std::queue<PTreeNode> nodeQue;
   nodeQue.push(m_pRoot);

   //广度遍历
   int nHeight = 0;
   while (!nodeQue.empty())
   {
       int nCurrentLevelSize = nodeQue.size();  // 记录当前层元素个数
       int nCnt = 0;
       std::cout << "第" << nHeight + 1 << "层:";
       while (nCnt < nCurrentLevelSize)
       {
           PTreeNode acessNode = nodeQue.front();
           nodeQue.pop();
           if (acessNode == m_pRoot)
           {
               std::cout << acessNode->nValue << "(根节点,颜色:" << s_pszColor[acessNode->eColor] << ")" << "  ";
           }
           else
           {
               if (acessNode->pParent->pLeft == acessNode)
               {
                   std::cout << "[(" << acessNode->nValue << "颜色:" << s_pszColor[acessNode->eColor] << ")"
                       << "(" << acessNode->pParent->nValue << "的左孩子)]"<< "  ";
               }
               else if (acessNode->pParent->pRight == acessNode)
               {
                   std::cout << "[(" << acessNode->nValue << "颜色:" << s_pszColor[acessNode->eColor] << ")"
                       << "(" << acessNode->pParent->nValue << "的右孩子)]" << "  ";
               }

           }

           //把下一层的元素放进来
           PTreeNode pLeft = acessNode->pLeft;
           PTreeNode pRight = acessNode->pRight;
           if (pLeft !=m_pNil)
           {
               nodeQue.push(pLeft);
           }
           if (pRight != m_pNil)
           {
               nodeQue.push(pRight);
           }
           ++nCnt;
       }
       nHeight++;
       std::cout << std::endl;
   }
   std::cout << std::endl;
}

void RBTree::InsertFixUp(PTreeNode pInsertNode)
{
   PTreeNode pFixNode = pInsertNode;
   //如果父亲是红节点(根节点的父亲节点为nil,一定为黑)
   while (E_TREE_RED == pFixNode->pParent->eColor)
   {
       //1. 如果调整节点的父亲为祖父节点的左孩子
       if (pFixNode->pParent == pFixNode->pParent->pParent->pLeft)
       {
           //获取叔叔节点(祖父节点的右孩子)
           PTreeNode pUncle = pFixNode->pParent->pParent->pRight;
           //1.1.如果叔叔节点为红色
           if (E_TREE_RED == pUncle->eColor)
           {
               //把父亲节点和叔叔节点都改为黑色
               pFixNode->pParent->eColor = E_TREE_BLACK;
               pUncle->eColor = E_TREE_BLACK;
               //把祖父节点改为红色
               pFixNode->pParent->pParent->eColor = E_TREE_RED;
               //重新计算调整节点为祖父节点
               pFixNode = pFixNode->pParent->pParent;
           }
           //1.2. 叔叔节点不为红色,且调整节点为父节点的右孩子,处理后会转化为1.3
           else if (pFixNode == pFixNode->pParent->pRight)
           {
               //从调整节点的父节点开始旋转
               pFixNode = pFixNode->pParent;
               //记录下新的顶点
               PTreeNode pNewTop = nullptr;
               SingleL(pFixNode->pParent->pLeft, pNewTop);
               //重新设置调整节点
               pFixNode = pNewTop->pLeft;
           }
           //1.3. 叔叔节点不为红色,且调整节点为父节点的左孩子
           else if (pFixNode == pFixNode->pParent->pLeft)
           {
               //把父亲节点变为黑色
               pFixNode->pParent->eColor = E_TREE_BLACK;
               //把祖父节点变为红色
               pFixNode->pParent->pParent->eColor = E_TREE_RED;
               //以祖父节点右旋转(注意到为根节点的情况)
               pFixNode = pFixNode->pParent->pParent;
               //记录下新的顶点
               PTreeNode pNewTop = nullptr;
               if (m_pRoot == pFixNode)
               {
                   SingleR(m_pRoot, pNewTop);
               }
               else if (pFixNode == pFixNode->pParent->pLeft)
               {
                   SingleR(pFixNode->pParent->pLeft, pNewTop);
               }
               else if (pFixNode == pFixNode->pParent->pRight)
               {
                   SingleR(pFixNode->pParent->pRight, pNewTop);
               }
               //重新设置调整点
               pFixNode = pNewTop->pLeft;
           }
       }
       //2. 如果调整节点的父亲为祖父节点的右孩子
       else if (pFixNode->pParent == pFixNode->pParent->pParent->pRight)
       {
           //获取叔叔节点(祖父节点的左孩子)
           PTreeNode pUncle = pFixNode->pParent->pParent->pLeft;
           //2.1 如果叔叔节点为红色
           if (E_TREE_RED == pUncle->eColor)
           {
               //把父亲节点和叔叔节点都改为黑色
               pFixNode->pParent->eColor = E_TREE_BLACK;
               pUncle->eColor = E_TREE_BLACK;
               //把祖父节点改为红色
               pFixNode->pParent->pParent->eColor = E_TREE_RED;
               //重新计算调整节点为祖父节点
               pFixNode = pFixNode->pParent->pParent;

           }
           //2.2 叔叔节点不为红色,且调整节点为父亲节点的左孩子,变为情况2.3
           else if (pFixNode == pFixNode->pParent->pLeft)
           {
               //从调整节点的父节点开始旋转
               pFixNode = pFixNode->pParent;
               //记录下新的顶点
               PTreeNode pNewTop = nullptr;
               SingleR(pFixNode->pParent->pRight, pNewTop);
               //重新设置调整节点
               pFixNode = pNewTop->pRight;

           }
           //2.3 叔叔节点不为红色,且调整节点为父亲节点的右边孩子
           else if (pFixNode == pFixNode->pParent->pRight)
           {
               //把父亲节点变为黑色
               pFixNode->pParent->eColor = E_TREE_BLACK;
               //把祖父节点变为红色
               pFixNode->pParent->pParent->eColor = E_TREE_RED;
               //以祖父节点左旋转(注意到为根节点的情况)
               pFixNode = pFixNode->pParent->pParent;
               //记录下新的顶点
               PTreeNode pNewTop = nullptr;
               if (m_pRoot == pFixNode)
               {
                   SingleL(m_pRoot, pNewTop);
               }
               else if (pFixNode == pFixNode->pParent->pLeft)
               {
                   SingleL(pFixNode->pParent->pLeft, pNewTop);
               }
               else if (pFixNode == pFixNode->pParent->pRight)
               {
                   SingleL(pFixNode->pParent->pRight, pNewTop);
               }
               //重新设置调整点
               pFixNode = pNewTop->pRight;
           }
       }
   }
   //在调整的过程中有可能修改了根节点的颜色为红色,需要修改为黑色
   m_pRoot->eColor = E_TREE_BLACK;

}

......代码过长,点击阅读原文去原帖查看完整代码


main.cpp


#include "RBTree.h"
#include <iostream>

int main(int argc, char * argv[])
{
   RBTree rbTree;
   //插入序列
   int nArryInsertValue[] = { 12, 1, 9, 2, 0, 11, 7, 19, 4, 15, 18, 5, 14, 13, 10, 16, 6, 3, 8, 17 };
   for (int i = 0; i < sizeof(nArryInsertValue) / sizeof(nArryInsertValue[0]); i++)
   {
       rbTree.InsertData(nArryInsertValue[i]);
   }
   //广度遍历
   rbTree.BreadthEnum();
   std::cout << "------------------------------" << std::endl;
   //删除序列
   for (int i = 0; i < sizeof(nArryInsertValue) / sizeof(nArryInsertValue[0]); i++)
   {
       std::cout << "删除" << nArryInsertValue[i] << "后" << std::endl;
       rbTree.DeleteElement(nArryInsertValue[i]);
       rbTree.BreadthEnum();
   }
   //插入任意序列
   std::cout << "插入任意序列" << std::endl;
   for (int i = 0; i < 100; i++)
   {
       rbTree.InsertData(i);
   }
   //查找3
   std::cout << "查找3" << std::endl;
   std::cout << "结果:"<< rbTree.FindElement(3) << std::endl;
   //广度遍历
   rbTree.BreadthEnum();
   std::cout << "------------------------------" << std::endl;

   //删除任意序列,只留三个
   for (int i = 99; i >= 3; i--)
   {
       rbTree.DeleteElement(i);
   }
   //广度遍历
   rbTree.BreadthEnum();
   std::cout << "------------------------------" << std::endl;
   return 0;
}



    运行结果


    插入结果(后面有图解)



   删除结果(后面有单步图解)

   


插入图解


插入结点:12、1、9、2、0、11、7、19、4、15、18、5、14、13、10、16、6、3、8、17 全程演示


1、插入节点12


2、插入节点1


3、插入结点9(左-情况2)


4、插入结点2(左-情况1)


5、插入结点0


6、插入结点11


7、插入结点7(右-情况1)


8、插入结点19


9、插入结点4(右-情况2)


10、插入结点15(右-情况1)


11、插入结点18(左-情况2)


12、插入结点5(右-情况1)


13、插入结点14(左-情况1)


14、插入结点13(左-情况3)


15、插入结点10


16-1、插入结点16(右-情况1) 

 


17、插入结点6(左-情况2)


18、插入结点3(左-情况2)


19、插入节点8


20、插入结点17(右-情况3)



删除图解

   

1、删除结点12(右-情况4)

   


2、删除结点1(左-情况4)


3、删除结点9(左-情况2)


5、删除结点0


6、删除结点11


7、删除结点7


8、删除结点19(右-情况4)


9、删除结点4(左-情况2)


10、删除结点15(左-情况3)

      

11、删除结点18(右-情况2)


12、删除结点5(左-情况4)


13、删除结点14(左-情况3)


14、删除结点13(左-情况2)


15、删除结点10


16、删除结点16(右-情况1)


17、删除结点6


18、删除结点3(左-情况2)


19、删除结点8


20、删除结点17

 





本文由看雪论坛 又出bug了 原创

转载请注明来自看雪社区



往期热门阅读:



点击阅读原文/read,

更多干货等着你~

扫描二维码关注我们,更多干货等你来拿!

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存